package blog.rupesr.spring.util;

import java.lang.reflect.Field;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import blog.rupesr.spring.annotation.BeanInjector;
import blog.rupesr.spring.annotation.Scope;

/**
 * The Class BeanInjectorPostProcessor.
 * 
 * @author Rupesh
 */
public final class BeanInjectorPostProcessor implements BeanPostProcessor, ApplicationContextAware {

	/** The log. */
	private Log log = LogFactory.getLog(getClass());

	/** The application context. */
	private ApplicationContext applicationContext;

	/** ApplicationContext instance. */
	private ApplicationContext jndiApplicationContext;

	/**
	 * Instantiates a new bean injector post processor.
	 */
	public BeanInjectorPostProcessor() {
		super();
	}

	/**
	 * Sets the application context.
	 * 
	 * @param inApplicationContext
	 *            the new application context
	 */
	public void setApplicationContext(final ApplicationContext inApplicationContext) {
		log.debug("setting inApplicationContext " + inApplicationContext);
		applicationContext = inApplicationContext;
	}

	/**
	 * Gets the application context.
	 * 
	 * @return the application context
	 */
	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	/**
	 * Gets the JNDI application context.
	 * 
	 * @return the JNDI application context
	 */
	public ApplicationContext getJndiApplicationContext() {
		return jndiApplicationContext;
	}

	/**
	 * Sets the JNDI application context.
	 * 
	 * @param inJndiApplicationContext
	 *            the new JNDI application context
	 */
	public void setJndiApplicationContext(final ApplicationContext inJndiApplicationContext) {
		log.debug("setting inJndiApplicationContext " + inJndiApplicationContext);
		jndiApplicationContext = inJndiApplicationContext;
	}

	/**
	 * Post process before initialization.
	 * 
	 * @param beanName
	 *            the bean name
	 * @param inBean
	 *            the in bean
	 * 
	 * @return the object
	 * 
	 * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object,
	 *      java.lang.String)
	 */
	public Object postProcessBeforeInitialization(final Object inBean, final String beanName) {
		Field[] fields = inBean.getClass().getDeclaredFields();
		for (Field field : fields) {
			if (field.isAnnotationPresent(BeanInjector.class)) {
				BeanInjector beanInjector = field.getAnnotation(BeanInjector.class);
				try {
					if (beanInjector.scope() == Scope.JNDI) {
						if (this.getJndiApplicationContext() == null) {
							throw new IllegalAccessException(new StringBuffer("for bean ").append(inBean.getClass().getName()).append(" >> ").append(
									field.getName()).append(" JNDI attribute is out of scope").toString());
						}// if
						BeanUtils.copyProperty(inBean, field.getName(), this.getJndiApplicationContext().getBean(beanInjector.beanName()));
					}// if
					if (beanInjector.scope() == Scope.LOCAL) {
						BeanUtils.copyProperty(inBean, field.getName(), this.getApplicationContext().getBean(beanInjector.beanName()));
					}// if
					if (beanInjector.scope() == Scope.SMART) {
						if (this.getJndiApplicationContext() != null && this.getJndiApplicationContext().getBean(beanInjector.beanName()) != null) {
							BeanUtils.copyProperty(inBean, field.getName(), this.getJndiApplicationContext().getBean(beanInjector.beanName()));
						} else if (this.getApplicationContext() != null && this.getApplicationContext().getBean(beanInjector.beanName()) != null) {
							BeanUtils.copyProperty(inBean, field.getName(), this.getApplicationContext().getBean(beanInjector.beanName()));
						} else {
							throw new IllegalAccessException(new StringBuffer("for bean ").append(inBean.getClass().getName()).append(" >> ").append(
									field.getName()).append(" Local/JNDI attribute is out of scope").toString());
						}
					}
				} catch (Exception e) {
					log.error(new StringBuffer("error occurred while injecting bean ").append(beanName).toString(), e);
				}// try-catch
			}// if
		}// for
		return inBean;
	}// postProcessBeforeInitialization()

	/**
	 * Post process after initialization.
	 * 
	 * @param inBean
	 *            the in bean
	 * @param beanName
	 *            the bean name
	 * 
	 * @return the object
	 * 
	 * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object,
	 *      java.lang.String)
	 */
	public Object postProcessAfterInitialization(final Object inBean, final String beanName) {
		return inBean;
	}// postProcessAfterInitialization()

}
